/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

/* 
    目标：写一个类来实现和Promise类一样的功能，要还原以下API
    then
    catch
    finally
    all
    resolve
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

const resolvePromise = (promise, x, resolve, reject) => {
    // 如果是返回自己那就抛出错误
    if (promise === x) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    if (x instanceof MyPromise) {
        x.then(resolve, reject)
    } else {
        resolve(x)
    }
}

class MyPromise {
    status = PENDING
    // 成功后的值
    value = undefined
    // 失败后的原因
    reason = undefined

    // 成功回调
    resolveCallback = []
    // 失败回调
    rejectCallback = []

    constructor(executor) {
        try {
            // 创建时调用传进来的执行器，并把改状态为成功和改状态为失败的方法传给执行器，以便其调用
            executor(this.resolve, this.reject)
        } catch (e) {
            this.reject(e)
        }
    }

    // 改状态为成功
    resolve = (value) => {
        // 如果不是pending状态就不让改状态，因为Promise状态一但更改不能再改了
        if (this.status !== PENDING) {
            return
        }
        // 将状态变为成功
        this.status = FULFILLED
        // 存下成功的值，以便传给成功的回调
        this.value = value
        // 同步执行时成功回调这里是拿不到的，主要是给异步调用时用，当异步改变状态时要去调用成功的回调。并把成功的数据传给它。
        // this.resolveCallback && this.resolveCallback(this.value)
        while (this.resolveCallback.length) {
            this.resolveCallback.shift()()
        }
    }

    // 改状态为失败
    reject = (reason) => {
        if (this.status !== PENDING) {
            return
        }
        // 将状态变为失败
        this.status = REJECTED
        // 存下失败的原因，以便传给失败的回调
        this.reason = reason
        // 同步执行时失败回调这里是拿不到的，主要是给异步调用时用，当异步改变状态时要去调用失败的回调。并把失败的原因传给它。
        // this.rejectCallback && this.rejectCallback(this.reason)
        while (this.rejectCallback.length) {
            this.rejectCallback.shift()()
        }
    }

    then(resolveCallback, rejectCallback) {
        resolveCallback = resolveCallback ? resolveCallback : value => value
        rejectCallback = rejectCallback ? rejectCallback : reason => { throw reason }
        let promise = new MyPromise((resolve, reject) => {
            // 如果调用then方法时状态已经变为成功了，那就说明同步调用了resolve方法，这时候需要调用成功回调，并把成功的数据传给它。
            if (this.status === FULFILLED) {
                // 延时为了取到promise的值
                queueMicrotask(() => {
                    try {
                        let resolveValue = resolveCallback(this.value)
                        resolvePromise(promise, resolveValue, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                })
            }
            // 如果调用then方法时状态已经变为失败了，那就说明同步调用了reject方法，这时候需要调用失败回调，并把失败的原因传给它。
            else if (this.status === REJECTED) {
                // 延时为了取到promise的值
                queueMicrotask(() => {
                    try {
                        let resolveValue = rejectCallback(this.reason)
                        resolvePromise(promise, resolveValue, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                })
            }
            // 如果状态还是pending，那就把成功和失败的回调存下来，等着给resolve和reject调用。
            else {
                this.resolveCallback.push(() => {
                    // 延时为了取到promise的值
                    queueMicrotask(() => {
                        try {
                            let resolveValue = resolveCallback(this.value)
                            resolvePromise(promise, resolveValue, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    })
                })
                this.rejectCallback.push(() => {
                    // 延时为了取到promise的值
                    queueMicrotask(() => {
                        try {
                            let resolveValue = rejectCallback(this.reason)
                            resolvePromise(promise, resolveValue, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    })
                })
            }
        })
        // 为了链式调用要返回一个MyPromise,而MyPromise接收的是立刻执行的方法
        return promise
    }

    catch (rejectCallback) {
        return this.then(undefined, rejectCallback)
    }

    finally(callback) {
        return this.then(value => {
            return MyPromise.resolve(callback()).then(() => value)
        }, reason => {
            return MyPromise.resolve(callback()).then(() => { throw reason })
        })
    }

    static all(array) {
        let result = []
        let index = 0

        return new MyPromise((resolve, reject) => {
            let addData = (key, value) => {
                result[key] = value
                index++
                if (array.length === index) {
                    resolve(result)
                }
            }
            for (let i = 0; i < array.length; i++) {
                let curren = array[i]
                if (curren instanceof MyPromise) {
                    curren.then(value => addData(value), reason => reject(reason))
                } else {
                    addData(i, curren)
                }
            }
        })
    }

    static resolve(value) {
        if (value instanceof MyPromise) {
            return value
        }
        return new MyPromise((resolve, reject) => {
            resolve(value)
        })
    }
}

module.exports = MyPromise